package main

import (
	"bytes"
	"encoding/json"
	"flag"
	"fmt"
	"io"
	"os"
	"regexp"
	"strconv"
	"strings"
	"sync"
	"sync/atomic"
	"time"

	"github.com/pkg/sftp"
	"golang.org/x/crypto/ssh"
)

// ===================== 1. 配置结构体定义（统一为StageCommand） =====================
// 设备专属参数
type DeviceParams map[string]interface{}

// 上传操作专属参数
type UploadOptions struct {
	LocalPath   string `json:"localPath"`   // 本地文件路径
	RemotePath  string `json:"remotePath"`  // 远程文件路径
	Permissions string `json:"permissions"` // 远程文件权限（如"0644"）
}

// 解压操作专属参数
type UnzipOptions struct {
	RemoteTarPath string `json:"remoteTarPath"` // 远程压缩包路径
	RemoteDestDir string `json:"remoteDestDir"` // 解压目标目录
	CleanTar      bool   `json:"cleanTar"`      // 解压后是否删除压缩包
}

// 统一的阶段操作结构
// Type字段支持：cmd(执行命令)、upload(上传文件)、unzip(解压文件)
type StageCommand struct {
	Desc        string         `json:"desc"`                 // 操作描述（如"停止服务"）
	Type        string         `json:"type"`                 // 操作类型：cmd/upload/unzip
	Cmd         string         `json:"cmd,omitempty"`        // 仅Type=cmd有效：命令（如"systemctl"）
	Args        []string       `json:"args,omitempty"`       // 仅Type=cmd有效：命令参数
	IgnoreError bool           `json:"ignoreError"`          // 所有类型通用：是否忽略错误
	UploadOpts  *UploadOptions `json:"uploadOpts,omitempty"` // 仅Type=upload有效
	UnzipOpts   *UnzipOptions  `json:"unzipOpts,omitempty"`  // 仅Type=unzip有效
	StageTag    string         `json:"stageTag"`             // 阶段标签：deploy(部署)/uninstall(卸载)
}

// 整体部署/卸载配置
type DeployConfig struct {
	Action        string         `json:"action"`        // 操作类型：deploy/uninstall
	Concurrency   int            `json:"concurrency"`   // 并发数
	DefaultParams DeviceParams   `json:"defaultParams"` // 全局默认参数
	Commands      []StageCommand `json:"commands"`      // 统一的操作列表
}

// 设备配置
type Device struct {
	Name     string       `json:"name"`
	IP       string       `json:"ip"`
	Port     int          `json:"port"`
	User     string       `json:"user"`
	Password string       `json:"password"`
	Params   DeviceParams `json:"params"`
}

// 全局配置
type GlobalConfig struct {
	Devices []Device     `json:"devices"`
	Deploy  DeployConfig `json:"deploy"`
}

// 执行结果
type ExecResult struct {
	DeviceName string
	Success    bool
	Msg        string
}

// 全局进度计数器（原子操作确保并发安全）
var (
	totalDevices int32
	completed    int32
	failed       int32
	printMutex   sync.Mutex // 避免进度打印混乱
)

// ===================== 2. 工具函数 =====================
// 加载配置文件
func loadConfig(filePath string) (GlobalConfig, error) {
	var config GlobalConfig
	file, err := os.Open(filePath)
	if err != nil {
		return config, fmt.Errorf("打开配置文件失败：%v", err)
	}
	defer file.Close()

	decoder := json.NewDecoder(file)
	if err := decoder.Decode(&config); err != nil {
		return config, fmt.Errorf("解析配置文件失败：%v", err)
	}

	// 校验操作类型（仅支持deploy/uninstall）
	if config.Deploy.Action != "deploy" && config.Deploy.Action != "uninstall" {
		return config, fmt.Errorf("action配置错误：仅支持deploy（部署）或uninstall（卸载）")
	}

	// 默认并发数：未配置则设为10
	if config.Deploy.Concurrency <= 0 {
		config.Deploy.Concurrency = 10
	}
	return config, nil
}

// 替换命令/参数中的占位符（支持{{key}}和{{key:default}}）
func replacePlaceholders(input string, deviceParams DeviceParams, defaultParams DeviceParams) string {
	re := regexp.MustCompile(`{{([^}:]+)(?::([^}]+))?}}`)

	return re.ReplaceAllStringFunc(input, func(match string) string {
		parts := re.FindStringSubmatch(match)
		if len(parts) < 2 {
			return match // 格式异常，保留原占位符
		}
		key := parts[1]
		defaultVal := ""
		if len(parts) == 3 {
			defaultVal = parts[2]
		}

		// 1. 优先取设备参数
		if val, ok := deviceParams[key]; ok {
			return fmt.Sprintf("%v", val)
		}

		// 2. 次优先取全局默认参数
		if val, ok := defaultParams[key]; ok {
			return fmt.Sprintf("%v", val)
		}

		// 3. 最后取默认值（若无则保留占位符并报警）
		if defaultVal != "" {
			return defaultVal
		}
		printMutex.Lock()
		fmt.Printf("⚠️  设备参数缺失：%s，保留原占位符\n", key)
		printMutex.Unlock()
		return match
	})
}

// 渲染命令模板（处理整条命令和参数）
func renderCommand(cmd StageCommand, device Device, defaultParams DeviceParams) (string, []string) {
	renderedCmd := replacePlaceholders(cmd.Cmd, device.Params, defaultParams)
	renderedArgs := make([]string, len(cmd.Args))
	for i, arg := range cmd.Args {
		renderedArgs[i] = replacePlaceholders(arg, device.Params, defaultParams)
	}
	return renderedCmd, renderedArgs
}

// 打印设备阶段状态
func printDeviceStage(deviceName, action, stageDesc, status string) {
	printMutex.Lock()
	defer printMutex.Unlock()

	prefix := ""
	switch status {
	case "开始":
		prefix = "🔄"
	case "成功":
		prefix = "✅"
	case "失败":
		prefix = "❌"
	case "忽略":
		prefix = "⚠️"
	}
	fmt.Printf("[%s][%s] %s - %s：%s\n", deviceName, action, prefix, stageDesc, status)
}

// 打印全局进度
func printGlobalProgress(action string) {
	printMutex.Lock()
	defer printMutex.Unlock()

	done := atomic.LoadInt32(&completed)
	fail := atomic.LoadInt32(&failed)
	processed := done + fail
	progress := float64(processed) / float64(totalDevices) * 100

	fmt.Printf("\r[全局进度][%s] 已处理：%d/%d | 成功：%d | 失败：%d | 进度：%.1f%% \n",
		action, processed, totalDevices, done, fail, progress)
}

// 校验远程路径是否存在
func remotePathExists(client *ssh.Client, path string) (bool, error) {
	session, err := client.NewSession()
	if err != nil {
		return false, fmt.Errorf("创建会话失败：%v", err)
	}
	defer session.Close()

	// 执行ls命令，检查路径是否存在
	cmd := fmt.Sprintf("ls -d %s > /dev/null 2>&1", path)
	if err := session.Run(cmd); err != nil {
		// 退出码2表示路径不存在，属于正常情况
		if exitErr, ok := err.(*ssh.ExitError); ok && exitErr.ExitStatus() == 2 {
			return false, nil
		}
		return false, fmt.Errorf("检查路径失败：%v", err)
	}
	return true, nil
}

// ===================== 3. SSH相关函数 =====================
// 创建SSH客户端
func createSSHClient(device Device) (*ssh.Client, error) {
	config := &ssh.ClientConfig{
		User: device.User,
		Auth: []ssh.AuthMethod{
			ssh.Password(device.Password),
		},
		HostKeyCallback: ssh.InsecureIgnoreHostKey(), // 生产环境建议替换为ssh.FixedHostKey
		Timeout:         10 * time.Second,
	}

	addr := fmt.Sprintf("%s:%d", device.IP, device.Port)
	client, err := ssh.Dial("tcp", addr, config)
	if err != nil {
		return nil, fmt.Errorf("SSH连接失败：%v", err)
	}
	return client, nil
}

// 执行远程命令
func execRemoteCmd(client *ssh.Client, cmd StageCommand, device Device, defaultParams DeviceParams) error {
	session, err := client.NewSession()
	if err != nil {
		return fmt.Errorf("创建SSH会话失败：%v", err)
	}
	defer session.Close()

	// 捕获命令输出（用于错误调试）
	var outputBuf bytes.Buffer
	session.Stdout = &outputBuf
	session.Stderr = &outputBuf

	// 渲染命令（替换占位符）
	renderedCmd, renderedArgs := renderCommand(cmd, device, defaultParams)
	// 拼接命令字符串（含参数）
	fullCmd := fmt.Sprintf("%s %s", renderedCmd, strings.Join(renderedArgs, " "))
	// 通过sh -c执行，支持管道、重定向等shell语法
	execCmd := fmt.Sprintf("sh -c '%s'", fullCmd)

	// 执行命令
	if err := session.Run(execCmd); err != nil {
		// 若配置IgnoreError，忽略错误并返回nil
		if cmd.IgnoreError {
			fmt.Printf("⚠️  %s：命令执行失败（已忽略），输出：%s\n", device.Name, outputBuf.String())
			return nil
		}
		// 不忽略错误，返回详细信息
		return fmt.Errorf("命令执行失败（输出：%s）：%v", outputBuf.String(), err)
	}
	return nil
}

// 上传文件到远程设备
func uploadRemoteFile(client *ssh.Client, uploadOpts UploadOptions, device Device, defaultParams DeviceParams) error {
	// 创建SFTP客户端
	sftpClient, err := sftp.NewClient(client)
	if err != nil {
		return fmt.Errorf("创建SFTP客户端失败：%v", err)
	}
	defer sftpClient.Close()

	// 渲染路径（支持占位符）
	localPath := replacePlaceholders(uploadOpts.LocalPath, device.Params, defaultParams)
	remotePath := replacePlaceholders(uploadOpts.RemotePath, device.Params, defaultParams)

	// 验证本地文件存在
	if _, err := os.Stat(localPath); os.IsNotExist(err) {
		return fmt.Errorf("本地文件不存在：%s", localPath)
	}

	// 打开本地文件
	localFile, err := os.Open(localPath)
	if err != nil {
		return fmt.Errorf("打开本地文件失败：%v", err)
	}
	defer localFile.Close()

	// 创建远程文件
	remoteFile, err := sftpClient.Create(remotePath)
	if err != nil {
		return fmt.Errorf("创建远程文件失败：%v", err)
	}
	defer remoteFile.Close()

	// 设置远程文件权限
	permStr := replacePlaceholders(uploadOpts.Permissions, device.Params, defaultParams)
	perm, err := strconv.ParseInt(permStr, 8, 32)
	if err != nil {
		return fmt.Errorf("解析文件权限失败（%s）：%v", permStr, err)
	}
	if err := remoteFile.Chmod(os.FileMode(perm)); err != nil {
		return fmt.Errorf("设置远程文件权限失败：%v", err)
	}

	// 传输文件
	if _, err := io.Copy(remoteFile, localFile); err != nil {
		return fmt.Errorf("文件传输失败：%v", err)
	}
	return nil
}

// 解压远程文件
func unzipRemoteFile(client *ssh.Client, unzipOpts UnzipOptions, device Device, defaultParams DeviceParams) error {
	// 渲染解压参数
	remoteTar := replacePlaceholders(unzipOpts.RemoteTarPath, device.Params, defaultParams)
	remoteDest := replacePlaceholders(unzipOpts.RemoteDestDir, device.Params, defaultParams)

	// 校验远程目录存在（不存在则创建）
	if exists, err := remotePathExists(client, remoteDest); err != nil {
		return fmt.Errorf("检查解压目录失败：%v", err)
	} else if !exists {
		mkdirCmd := StageCommand{
			Desc: "创建解压目录",
			Type: "cmd",
			Cmd:  "mkdir",
			Args: []string{"-p", remoteDest},
		}
		if err := execRemoteCmd(client, mkdirCmd, device, defaultParams); err != nil {
			return fmt.Errorf("创建解压目录失败：%v", err)
		}
	}

	// 执行解压命令
	unzipCmd := StageCommand{
		Desc: "执行文件解压",
		Type: "cmd",
		Cmd:  "tar",
		Args: []string{"-zxf", remoteTar, "-C", remoteDest},
	}
	if err := execRemoteCmd(client, unzipCmd, device, defaultParams); err != nil {
		return fmt.Errorf("解压失败：%v", err)
	}

	// 解压后删除压缩包（若配置开启）
	if unzipOpts.CleanTar {
		rmTarCmd := StageCommand{
			Desc:        "删除压缩包",
			Type:        "cmd",
			Cmd:         "rm",
			Args:        []string{"-f", remoteTar},
			IgnoreError: true,
		}
		if err := execRemoteCmd(client, rmTarCmd, device, defaultParams); err != nil {
			fmt.Printf("⚠️  %s：压缩包删除失败（不影响服务）：%v\n", device.Name, err)
		}
	}

	return nil
}

// 上传适配函数
func uploadRemoteFileAdapt(client *ssh.Client, cmd StageCommand, device Device, defaultParams DeviceParams) error {
	return uploadRemoteFile(client, *cmd.UploadOpts, device, defaultParams)
}

// 解压适配函数
func unzipRemoteFileAdapt(client *ssh.Client, cmd StageCommand, device Device, defaultParams DeviceParams) error {
	return unzipRemoteFile(client, *cmd.UnzipOpts, device, defaultParams)
}

// ===================== 4. 核心流程 =====================
// 单设备执行所有目标操作
func execSingleDevice(device Device, deployConfig DeployConfig, commands []StageCommand) ExecResult {
	action := deployConfig.Action

	// 1. 创建SSH连接
	client, err := createSSHClient(device)
	if err != nil {
		return ExecResult{
			DeviceName: device.Name,
			Success:    false,
			Msg:        fmt.Sprintf("SSH连接失败：%v", err),
		}
	}
	defer client.Close()

	// 2. 按顺序执行所有操作
	for _, cmd := range commands {
		printDeviceStage(device.Name, action, cmd.Desc, "开始")
		var execErr error

		// 根据Type分发操作
		switch cmd.Type {
		case "cmd":
			// 执行命令
			execErr = execRemoteCmd(client, cmd, device, deployConfig.DefaultParams)
		case "upload":
			// 上传文件
			if cmd.UploadOpts == nil {
				execErr = fmt.Errorf("上传操作缺少uploadOpts配置")
				break
			}
			execErr = uploadRemoteFileAdapt(client, cmd, device, deployConfig.DefaultParams)
		case "unzip":
			// 解压文件
			if cmd.UnzipOpts == nil {
				execErr = fmt.Errorf("解压操作缺少unzipOpts配置")
				break
			}
			execErr = unzipRemoteFileAdapt(client, cmd, device, deployConfig.DefaultParams)
		default:
			execErr = fmt.Errorf("不支持的操作类型：%s", cmd.Type)
		}

		// 处理执行结果
		if execErr != nil {
			if cmd.IgnoreError {
				printDeviceStage(device.Name, action, cmd.Desc, "忽略")
				fmt.Printf("⚠️  %s：%s（已忽略）：%v\n", device.Name, cmd.Desc, execErr)
				continue
			}
			printDeviceStage(device.Name, action, cmd.Desc, "失败")
			return ExecResult{
				DeviceName: device.Name,
				Success:    false,
				Msg:        fmt.Sprintf("%s失败：%v", cmd.Desc, execErr),
			}
		}
		printDeviceStage(device.Name, action, cmd.Desc, "成功")
	}

	// 3. 执行完成
	atomic.AddInt32(&completed, 1)
	printGlobalProgress(action)
	return ExecResult{
		DeviceName: device.Name,
		Success:    true,
		Msg:        fmt.Sprintf("%s完成", strings.Title(action)),
	}
}

// ===================== 5. 批量部署/卸载控制 =====================
func batchAction(config GlobalConfig) {
	action := config.Deploy.Action
	// 初始化进度计数器
	totalDevices = int32(len(config.Devices))
	completed = 0
	failed = 0

	// 结果通道
	resultChan := make(chan ExecResult, totalDevices)
	// 并发控制通道
	semaphore := make(chan struct{}, config.Deploy.Concurrency)

	// 过滤当前操作（deploy/uninstall）对应的命令
	targetCommands := make([]StageCommand, 0)
	for _, cmd := range config.Deploy.Commands {
		if cmd.StageTag == action {
			targetCommands = append(targetCommands, cmd)
		}
	}
	if len(targetCommands) == 0 {
		fmt.Printf("⚠️  未找到 %s 类型的操作配置，直接退出\n", action)
		return
	}

	// 启动所有设备执行协程
	for _, device := range config.Devices {
		semaphore <- struct{}{}
		go func(d Device) {
			defer func() {
				// 释放并发名额
				<-semaphore
				// 捕获协程panic
				if r := recover(); r != nil {
					errMsg := fmt.Sprintf("协程异常：%v", r)
					resultChan <- ExecResult{
						DeviceName: d.Name,
						Success:    false,
						Msg:        errMsg,
					}
					atomic.AddInt32(&failed, 1)
					printGlobalProgress(action)
				}
			}()

			// 执行单设备操作
			result := execSingleDevice(d, config.Deploy, targetCommands)
			if !result.Success {
				atomic.AddInt32(&failed, 1)
				printGlobalProgress(action)
			}
			resultChan <- result
		}(device)
	}

	// 等待所有设备执行完成并收集结果
	var results []ExecResult
	for i := 0; i < int(totalDevices); i++ {
		results = append(results, <-resultChan)
	}
	close(resultChan)
	close(semaphore)

	// 输出最终执行报告
	printMutex.Lock()
	defer printMutex.Unlock()
	fmt.Println("\n\n" + strings.Repeat("=", 60))
	if action == "deploy" {
		fmt.Println("          批量部署最终报告")
	} else {
		fmt.Println("          批量卸载最终报告")
	}
	fmt.Println(strings.Repeat("=", 60))
	fmt.Printf("总设备数：%d | 成功：%d | 失败：%d\n", totalDevices, completed, failed)
	fmt.Println(strings.Repeat("-", 60))

	// 输出失败设备详情
	if failed > 0 {
		fmt.Printf("❌ %s失败设备详情：\n", strings.Title(action))
		for _, res := range results {
			if !res.Success {
				fmt.Printf("  %s：%s\n", res.DeviceName, res.Msg)
			}
		}
	} else {
		if action == "deploy" {
			fmt.Println("✅ 所有设备部署成功！")
		} else {
			fmt.Println("✅ 所有设备卸载成功！")
		}
	}
	fmt.Println(strings.Repeat("=", 60))
}

// ===================== 6. 主函数（程序入口） =====================
func main() {
	// 1. 定义命令行参数
	var (
		configPath string
		action     string
	)

	// 配置文件路径参数（默认值设为config.json）
	flag.StringVar(&configPath, "f", "config.json", "配置文件路径，默认：config.json")
	// 操作类型参数（可选，优先级高于配置文件）
	flag.StringVar(&action, "t", "", "操作类型（可选）：deploy（部署）/uninstall（卸载），优先级高于配置文件")

	// 自定义帮助信息
	flag.Usage = func() {
		fmt.Println("用法：servicectl [-f <配置文件路径>] [-t <deploy/uninstall>]")
		fmt.Println("示例：")
		fmt.Println("  使用默认配置文件：./servicectl")
		fmt.Println("  指定配置文件：./servicectl -f custom_config.json")
		fmt.Println("  指定操作类型：./servicectl -t uninstall")
		fmt.Println("  完整用法：./servicectl -f custom_config.json -t deploy")
		fmt.Println("参数说明：")
		flag.PrintDefaults()
	}
	flag.Parse()

	// 2. 验证配置文件是否存在
	if _, err := os.Stat(configPath); os.IsNotExist(err) {
		fmt.Printf("❌ 配置文件不存在：%s\n", configPath)
		flag.Usage()
		os.Exit(1)
	}

	// 3. 加载配置文件
	fmt.Printf("🔍 正在加载配置文件：%s...\n", configPath)
	config, err := loadConfig(configPath)
	if err != nil {
		fmt.Printf("❌ 配置加载失败：%v\n", err)
		os.Exit(1)
	}
	if len(config.Devices) == 0 {
		fmt.Println("❌ 配置文件中未发现设备信息")
		os.Exit(1)
	}

	// 4. 处理命令行指定的action（覆盖配置文件）
	finalAction := config.Deploy.Action
	if action != "" {
		if action != "deploy" && action != "uninstall" {
			fmt.Printf("❌ 命令行-action参数错误：仅支持deploy或uninstall，您输入的是%s\n", action)
			os.Exit(1)
		}
		fmt.Printf("ℹ️  命令行指定action：%s（已覆盖配置文件中的%s）\n", action, finalAction)
		finalAction = action
		config.Deploy.Action = finalAction // 更新配置中的action
	}

	// 5. 显示执行基本信息
	fmt.Printf("📋 %s基本信息：\n", strings.Title(finalAction))
	fmt.Printf("  - 目标设备数：%d\n", len(config.Devices))
	fmt.Printf("  - 并发%s数：%d\n", finalAction, config.Deploy.Concurrency)

	// 统计当前操作的命令数量
	cmdCount := 0
	for _, cmd := range config.Deploy.Commands {
		if cmd.StageTag == finalAction {
			cmdCount++
		}
	}
	fmt.Printf("  - 执行命令数：%d\n", cmdCount)

	fmt.Println(strings.Repeat("=", 60))
	fmt.Printf("🚀 开始批量%s（按Ctrl+C可终止）...\n", finalAction)
	fmt.Println()

	// 6. 启动批量执行
	batchAction(config)
}
